package com.itheima.service.impl;

import com.itheima.commons.pojo.CommonEntity;
import com.itheima.commons.utils.SearchTools;
import com.itheima.service.ElasticsearchDocumentService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.bouncycastle.cert.ocsp.Req;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.ScoreSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestionBuilder;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
import org.elasticsearch.search.suggest.phrase.PhraseSuggestion;
import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder;
import org.elasticsearch.search.suggest.term.TermSuggestion;
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @program: itheima-technology-platform
 * @description: 全文检索的实现
 * @author: zhanghz001
 * @create: 2021-05-15 09:56
 **/
// @Service
//    热部署更新
//     @RefreshScope
@Slf4j
public class ZhzElasticsearchDocumentServiceImpl implements ElasticsearchDocumentService {
    @Autowired
    private RestHighLevelClient client;
    
    /**
     * 全文检索,matchQuery搜索的时候会被分词器分词
     * <p>
     * 返回
     *
     * @param commonEntity
     * @return
     * @throws Exception
     */
    @Override
    public SearchResponse matchQuery(CommonEntity commonEntity) throws Exception {
        //查询条件构造
        //# 查询的总数不会是10000,而是其他数量
        // GET product/_search
        // {
        //   "track_total_hits":true,
        //   "from": 0,
        //   "size": 20,
        //   "query": {
        //     "match": {
        //       "productname": {
        //         "query": "华为全面屏",
        //         "operator": "and"
        //       } 
        //     }
        //   },
        //   "highlight": {
        //     "fields": {
        //       "productname": {
        //         "pre_tags": "<font color='red'>",
        //         "post_tags": "</font>"
        //       }
        //     }
        //   }
        // } 
        //2 获取查询的index
        SearchRequest searchRequest = new SearchRequest(commonEntity.getIndexName());
        
        //4构造查询条件构造器
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        
        //4.1其他查询条件"track_total_hits":true
        sourceBuilder.trackTotalHits(true);
        
        //4.2设置分页信息
        int pageSize = commonEntity.getPageSize();
        sourceBuilder.size(pageSize);
        int pageNumber = commonEntity.getPageNumber();
        int from = (pageNumber - 1) * pageSize;
        sourceBuilder.from(from);
        
        //4.3.1 高亮新街
        //6.2:highlight
        HighlightBuilder highlightBuilder = SearchTools
                .getHighlightBuilder(commonEntity.getHighlight());
        //4.3 设置高亮信息
        sourceBuilder.highlighter(highlightBuilder);
        
        //6获取查询条件各种
        getClientConditions(commonEntity, sourceBuilder);
        
        //3获取条件构造器
        searchRequest.source(sourceBuilder);
        //1调用search方法,获取结果
        long start = System.currentTimeMillis();
        log.info("开始es的查询matchQuery ...");
        SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
        
        log.info("结束Elasticsearch查询总耗时: {} ms", System.currentTimeMillis() - start);
        //返回结果
        //{
        //   "took" : 42,
        //   "timed_out" : false,
        //   "_shards" : {
        //     "total" : 1,
        //     "successful" : 1,
        //     "skipped" : 0,
        //     "failed" : 0
        //   },
        //   "hits" : {
        //     "total" : {
        //       "value" : 889,
        //       "relation" : "eq"
        //     },
        //     "max_score" : 16.118246,
        //     "hits" : [
        //       {
        //         "_index" : "product",
        //         "_type" : "_doc",
        //         "_id" : "197520",
        //         "_score" : 16.118246,
        //         "_source" : {
        //           "@version" : "1",
        //           "@timestamp" : "2021-05-14T09:34:15.376Z",
        //           "id" : 197520,
        //           "productname" : "\n华为（HUAWEI）华为mate20pro手机全面屏亮黑色全网通8GB+128GB",
        //           "price" : 0
        //         },
        //         "highlight" : {
        //           "productname" : [
        //             "<font color='red'>华</font><font color='red'>为</font>（HUAWEI）<font color='red'>华</font><font color='red'>为</font>mate20pro手机<font color='red'>全</font><font color='red'>面</font><font color='red'>屏</font>亮黑色<font color='red'>全</font>网通8GB+128GB"
        //           ]
        //         }
        //       },
        
        //获取hits,命中对象
        SearchHits hits = response.getHits();
        //获取总数
        long total = hits.getTotalHits().value;
        log.info("华为全面屏总数:{}", total);
        
        //高亮处理
        SearchTools.setHighResultForCleintUI(response, commonEntity.getHighlight());
        
        return response;
    }
    
    @Override
    public SearchResponse termQuery(CommonEntity commonEntity) throws Exception {
        //构建查询响应
        SearchResponse response = null;
        //构建查询请求用来完成和搜索文档，聚合，建议等相关的任何操作同时也提供了各种方式来完成对查询结果的高亮操作。
        SearchRequest searchRequest = new SearchRequest(commonEntity.getIndexName());
        //
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //构建DSL请求体trackTotalHits如果不设置true，查询数据最大值还是10000
        sourceBuilder.trackTotalHits(true);
        //将前端的dsl查询转化为XContentParser
        XContentParser xContentParser = SearchTools.getXContentParser(commonEntity);
        //将parse接线处查询API
        sourceBuilder.parseXContent(xContentParser);
        //高亮设置
        HighlightBuilder builder = SearchTools.getHighlightBuilder(commonEntity.getHighlight());
        sourceBuilder.highlighter(builder);
        
        //分页
        int pageNumber = commonEntity.getPageNumber();
        int pageSize = commonEntity.getPageSize();
        int from = (pageNumber - 1) * pageSize;
        sourceBuilder.from(from);
        sourceBuilder.size(pageSize);
        
        //排序
        sort(commonEntity, sourceBuilder);
        
        //查询条件对象加入请求对象中
        searchRequest.source(sourceBuilder);
        
        //方法执行开始时间
        long startTime = System.currentTimeMillis();
        //执行远程查询
        response = client.search(searchRequest, RequestOptions.DEFAULT);
        //远程查询耗时
        log.info("查询耗时:{}", System.currentTimeMillis() - startTime);
        
        //处理高亮
        SearchTools.setHighResultForCleintUI(response, commonEntity.getHighlight());
        return response;
    }
    
    @Override
    public RestStatus bulkAddDoc(CommonEntity commonEntity) throws Exception {
        
        //通过索引构建批量请求对象
        BulkRequest bulkRequest = new BulkRequest(commonEntity.getIndexName());
        
        //循环前台list文档数据
        for (int i = 0; i < commonEntity.getList().size(); i++) {
            Object[] xcontent = SearchTools.mapToObjectGroup(commonEntity.getList().get(i));
            
            IndexRequest source = new IndexRequest().source(XContentType.JSON, xcontent);
            bulkRequest.add(source);
        }
        //执行批量新增
        BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
        return bulkResponse.status();
    }
    
    @Override
    public String pSuggest(CommonEntity commonEntity) throws Exception {
        //创建查询对象
        SearchRequest searchRequest = new SearchRequest(commonEntity.getIndexName());
        
        //查询条件的构建
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        
        //定义排序
        sourceBuilder.sort(new ScoreSortBuilder().order(SortOrder.DESC));
        
        //纠错的类型是拼写
        //匹配列
        PhraseSuggestionBuilder phraseSuggestionBuilder = new PhraseSuggestionBuilder(
                commonEntity.getSuggestFileld());
        //匹配搜索关键字
        phraseSuggestionBuilder.text(commonEntity.getSuggestValue());
        //匹配输入
        phraseSuggestionBuilder.size(1);
        //
        //创建建议对象
        SuggestBuilder suggestionBuilder = new SuggestBuilder();
        suggestionBuilder.addSuggestion("czbk-suggest", phraseSuggestionBuilder);
        
        //创建元构建器
        sourceBuilder.suggest(suggestionBuilder);
        //
        //拼写纠错的条件
        //构建查询条件
        searchRequest.source(sourceBuilder);
        
        //执行查询的过程
        SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
        //定义语句建议对象
        PhraseSuggestion suggestion = response.getSuggest().getSuggestion("czbk-suggest");
        //获取返回数据
        List<PhraseSuggestion.Entry.Option> options =
                suggestion.getEntries().get(0).getOptions();
        //从options中取出对象
        String result = null;
        if (!CollectionUtils.isEmpty(options)) {
            //结果取出
            result = options.get(0).getText().string();
        }
        return result;
    }
    
    @Override
    public String tSuggest(CommonEntity commonEntity) throws Exception {
        
        //构建查询对象
        SearchRequest searchRequest = new SearchRequest(commonEntity.getIndexName());
        
        //构建查询条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //根据分数排序
        sourceBuilder.sort(new ScoreSortBuilder().order(SortOrder.DESC));
        
        //搜索推荐对象
        SuggestBuilder suggestBuilder = new SuggestBuilder();
        //搜索推荐对象的列
        TermSuggestionBuilder suggestion = new TermSuggestionBuilder(
                commonEntity.getSuggestFileld());
        //加入需要搜索推荐的值
        suggestion.text(commonEntity.getSuggestValue());
        //最小推荐字符串
        suggestion.minWordLength(2);
        
        //输入字符串距离算法
        suggestion.stringDistance(TermSuggestionBuilder.StringDistanceImpl.NGRAM);
        //ik分词
        suggestion.analyzer("ik_smart");
        
        //将词条建议器加入到查询构建器中
        suggestBuilder.addSuggestion("czbk-suggest", suggestion);
        
        //加入搜索推荐对象
        sourceBuilder.suggest(suggestBuilder);
        
        //加入查询条件
        searchRequest.source(sourceBuilder);
        //执行查询
        SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
        
        //返回建议对象
        TermSuggestion termSuggest = response.getSuggest().getSuggestion("czbk-suggest");
        
        //定义返回
        String result = null;
        //推荐的列表
        List<TermSuggestion.Entry> entryList = termSuggest.getEntries();
        if (!CollectionUtils.isEmpty(entryList)) {
            List<TermSuggestion.Entry.Option> options = entryList.get(0).getOptions();
            if (!CollectionUtils.isEmpty(options)) {
                //选择第一个推荐的第一项返回
                result = options.get(0).getText().string();                
            }
        }
        return result;
    }
    
    /**
     * 自动补全
     *
     * @param commonEntity
     * @return
     * @throws Exception
     */
    @Override
    public List<String> cSuggest(CommonEntity commonEntity) throws Exception {
        //构建查询条件
        SearchRequest searchRequest = new SearchRequest(commonEntity.getIndexName());
        
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        
        //排序
        sourceBuilder.sort(new ScoreSortBuilder().order(SortOrder.DESC));
        
        //构造搜索建议语句,搜索添加字段
        
        //自动补全条件构造器
        SuggestBuilder suggestBuilder = new SuggestBuilder();
        CompletionSuggestionBuilder completionSuggestionBuilder = new CompletionSuggestionBuilder(
                commonEntity.getSuggestFileld());
        //搜索关键字
        completionSuggestionBuilder.prefix(commonEntity.getSuggestValue());
        //查询个数
        completionSuggestionBuilder.size(commonEntity.getSuggestCount());
        //取出重复
        completionSuggestionBuilder.skipDuplicates(true);
        suggestBuilder.addSuggestion("czbk-suggest", completionSuggestionBuilder);
        
        //自动补全的条件
        sourceBuilder.suggest(suggestBuilder);
        
        //查询添加构建器加入查询请求
        searchRequest.source(sourceBuilder);
        //到es查询到自动补全信息
        SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
        
        //定义完成建议对象
        CompletionSuggestion suggestion = response.getSuggest().getSuggestion("czbk-suggest");
        List<CompletionSuggestion.Entry.Option> optionList = suggestion.getEntries().get(0).getOptions();
        List<String> suggestList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(optionList)) {
            optionList.forEach(option -> suggestList.add(option.getText().toString()));
        }
        //
        return suggestList;
    }
    
    /**
     * @Description: 排序
     * @Method: sort
     * @Param: [commonEntity, searchSourceBuilder]
     * @Update:
     * @since: 1.0.0
     * @Return: void
     */
    private void sort(CommonEntity commonEntity, SearchSourceBuilder
            searchSourceBuilder) {
        String sortField = commonEntity.getSortField();
        if (StringUtils.isNotEmpty(sortField)) {
            //排序,获取前端的order by子句，不区分大小写，参数为空则默认desc
            SortOrder sortOrder =
                    SearchTools.getSortOrder(commonEntity.getSortOrder());
            //执行排序
            searchSourceBuilder.sort(new
                    FieldSortBuilder(commonEntity.getSortField()).order(sortOrder));
        }
    }
    
    /*
     * @Description: 获取前端的查询条件
     * @Method: getClientConditions
     * @Param: [commonEntity, searchSourceBuilder]
     * @Update:
     * @since: 1.0.0
     * @Return: void
     *
     */
    private void getClientConditions(CommonEntity commonEntity, SearchSourceBuilder searchSourceBuilder) {
        //循环前端的查询条件
        for (Map.Entry<String, Object> m : commonEntity.getMap().entrySet()) {
            if (StringUtils.isNotEmpty(m.getKey()) && m.getValue() != null) {
                String key = m.getKey();
                String value = String.valueOf(m.getValue());
                //构造请求体中“query”:{}部分的内容 ,QueryBuilders静态工厂类，方便构造queryBuilder
                //将搜索词分词，再与目标查询字段进行匹配，若分词中的任意一个词与目标字段匹配上，则可查询到。
                searchSourceBuilder.query(QueryBuilders.matchQuery(key, value));
                log.info("search for the keyword:" + value);
            }
        }
    }
}
